package org.xqh.study.leetcode.mulitthread;

import java.util.concurrent.Semaphore;
import java.util.function.IntConsumer;

/**
 * @ClassName FizzBuzzSemaphore
 * @Description 信号量
 * @Author xuqianghui
 * @Date 2020/12/25 21:57
 * @Version 1.0
 */
public class FizzBuzzSemaphore {

    static class FizzBuzz {
        private int n;
        private int c;

        Semaphore s = new Semaphore(1);
        Semaphore s1 = new Semaphore(0);
        Semaphore s2 = new Semaphore(0);
        Semaphore s3 = new Semaphore(0);
        Semaphore s4 = new Semaphore(0);

        public FizzBuzz(int n) {
            this.n = n;
            this.c = 1;
        }

        // printFizz.run() outputs "fizz".
        public void fizz(Runnable printFizz) throws InterruptedException {
            for (;;){
                if(c % 3 != 0 || (c % 3 == 0 && c % 5 == 0)){
                    s1.acquire();//block
                }
                if(c > n){
                    releaseAll();
                    break;
                }
                if(c % 3 == 0 && c % 5 != 0){
                    s.acquire();
                    printFizz.run();
//                    System.out.println("fizz");
                    c ++;
                    s.release();
                    releaseAll();
                }
            }
        }

        public void releaseAll(){
            s1.release();
            s2.release();
            s3.release();
            s4.release();
        }

        // printBuzz.run() outputs "buzz".
        public void buzz(Runnable printBuzz) throws InterruptedException {
            for (;;){
                if(c % 5 != 0 || (c % 3 == 0 && c % 5 == 0)){
                    s2.acquire();//block
                }
                if(c > n){
                    releaseAll();
                    break;
                }
                if(c % 5 == 0 && c % 3 != 0){
                    s.acquire();
                    printBuzz.run();
//                    System.out.println("puzz");
                    c ++;
                    s.release();
                    releaseAll();
                }
            }
        }

        // printFizzBuzz.run() outputs "fizzbuzz".
        public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
            for (;;){
                if(c % 5 != 0 || c % 3 != 0){
                    s3.acquire();//block
                }
                if(c > n){
                    releaseAll();
                    break;
                }
                if(c % 5 == 0 && c % 3 == 0){
                    s.acquire();
                    printFizzBuzz.run();
//                    System.out.println("fizzbuzz");
                    c ++;
                    s.release();
                    releaseAll();
                }
            }
        }

        // printNumber.accept(x) outputs "x", where x is an integer.
        public void number(IntConsumer intConsumer) throws InterruptedException {
            for (;;){
                if(c % 5 == 0 || c % 3 == 0){
                    s4.acquire();//block
                }
                if(c > n){
                    releaseAll();
                    break;
                }
                if(c % 5 != 0 && c % 3 != 0){
                    s.acquire();
                    intConsumer.accept(c);
                    c ++;
                    s.release();
                    releaseAll();
                }
            }
        }
    }

    public static void main(String[] args) {
        FizzBuzz fb = new FizzBuzz(15);
        new Thread(new Thread1(fb)).start();
        new Thread(new Thread2(fb)).start();
        new Thread(new Thread3(fb)).start();
        new Thread(new Thread4(fb)).start();
    }

    public static class Thread1 implements Runnable{
        private FizzBuzz fb;
        public Thread1(FizzBuzz fb){
            this.fb = fb;
        }

        @Override
        public void run() {
            try {
                fb.fizz(this);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class Thread2 implements Runnable{
        private FizzBuzz fb;
        public Thread2(FizzBuzz fb){
            this.fb = fb;
        }

        @Override
        public void run() {
            try {
                fb.buzz(this);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class Thread3 implements Runnable{
        private FizzBuzz fb;
        public Thread3(FizzBuzz fb){
            this.fb = fb;
        }

        @Override
        public void run() {
            try {
                fb.fizzbuzz(this);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class Thread4 implements Runnable{
        private FizzBuzz fb;
        public Thread4(FizzBuzz fb){
            this.fb = fb;
        }

        @Override
        public void run() {
            try {
                fb.number(new IntConsumer() {
                    @Override
                    public void accept(int value) {

                    }
                });
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
